const std = @import("std");

pub const Input = union(enum) {
    File: std.fs.File,
    Stdin: std.io.BufferedInputStream(std.os.STDIN_STREAM),

    pub fn read(self: *Input, buffer: []u8) !usize {
        return switch (self) {
            .File => |file| file.read(buffer),
            .Stdin => |stdin| stdin.read(buffer),
        };
    }

    pub fn seek(self: *Input, pos: std.fs.SeekPos) !u64 {
        return switch (self) {
            .File => |file| seekFile(file, pos),
            .Stdin => |_| seekStdin(pos),
        };
    }

    pub fn intoInner(self: Input) *std.mem.Allocator {
        return switch (self) {
            .File => std.mem.direct_allocator, // Placeholder for allocation
            .Stdin => std.mem.direct_allocator, // Placeholder for allocation
        };
    }

    fn seekFile(file: *std.fs.File, pos: std.fs.SeekPos) !u64 {
        var seek_result = file.seek(pos);
        if (seek_result == error.Stream) {
            if (pos.start == .Current) {
                const offset = @intCast(u64, pos.offset);
                return skipBytes(file.reader(), offset);
            } else {
                return error.InvalidSeek;
            }
        }
        return seek_result;
    }

    fn seekStdin(pos: std.fs.SeekPos) !u64 {
        if (pos.start != .Current) {
            return error.InvalidSeek;
        }
        const offset = @intCast(u64, pos.offset);
        return skipBytes(std.io.BufferedInputStream(std.os.STDIN_STREAM), offset);
    }

    fn skipBytes(reader: anytype, count: u64) !u64 {
        var buffer: [256]u8 = undefined;
        var remaining = count;
        while (remaining > 0) {
            const chunk_size = @min(remaining, buffer.len);
            const bytes_read = try reader.read(buffer[0..chunk_size]);
            if (bytes_read == 0) break;
            remaining -= bytes_read;
        }
        return count - remaining;
    }
};
